﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Practices.Prism.ViewModel;
using Microsoft.Practices.Prism.Events;

using Hockey.Shared;
using Hockey.UILayer.ViewModels;
using Hockey.UILayer.Forms;
using System.Windows.Input;
using Microsoft.Practices.Unity;
using MVVMDialogs;
using System.Windows.Threading;

namespace Hockey.ViewModels
{
    /// <summary>
    /// The main window view model - contains the main playing logic
    /// </summary>
    public class MainViewModel: NotificationObject
    {
        private IEventAggregator _events;
        private IBoard _board;
        private IUnityContainer _unity;
        private IDialogService _dlgService;
        private Dispatcher _dispatcher;

        private eMoveDirection _moveDirection;
        private bool isFinal;
        private IEngine _engine;
        private List<eMoveDirection> _engineMove;
        private bool _engineMoveFlag = false;
        private GameSettings _gameSettings;

        /// <summary>
        /// Constructor
        /// </summary>
        public MainViewModel(IBoard board, IEventAggregator events, IEngine engine, IUnityContainer unity, IDialogService dlgService)
        {
            _dispatcher = Dispatcher.CurrentDispatcher;

            if (board == null)
                throw new ArgumentNullException("Board");
            _board = board;

            if (events == null)
                throw new ArgumentNullException("Event Aggregator");
            _events = events;

            if (engine == null)
                throw new ArgumentNullException("Engine");       
            _engine = engine;
            _engine.MoveComputedEvent += Engine_MoveComputedEventHandler;

            if (unity == null)
                throw new ArgumentNullException("Unity");
            _unity = unity;

            if (dlgService == null)
                throw new ArgumentNullException("DialogService");
            _dlgService = dlgService;

            _engineMove = new List<eMoveDirection>();

            // register events
            _events.GetEvent<MoveStepFired>().Subscribe((dir) => { MoveStepFiredHandler(dir); }, true);
            _events.GetEvent<MoveAnimationFinished>().Subscribe((obj) => { PuckAnimationFinished(); }, true);
            _events.GetEvent<StartNewGame>().Subscribe((settings) => { StartNewGame(settings); }, true);
            _events.GetEvent<ReturningPlayerName>().Subscribe((name) => { ShowEndGameForm(name); }, true);
            _events.GetEvent<RotationAnimationFinished>().Subscribe((obj) => { RotationAnimationFinished(); }, true);

            // call to create the new game with default parametres
            GameSettings gameSettings;
            gameSettings.GameMode = eGameMode.ComputerGame;
            gameSettings.EngineLevel = eEngineLevel.Novice;
            gameSettings.FirstToMove = eFirstToMove.Player;

            _events.GetEvent<StartNewGame>().Publish(gameSettings);
        }


        /// <summary>
        /// Shows the end game dialog
        /// </summary>
        private void ShowEndGameForm(string playerName)
        {
            EndGameFormVM vm = _unity.Resolve<EndGameFormVM>();
            vm.PlayerName = playerName;
            _dlgService.ShowDialog<EndGameNotification>(this, vm);
        }

        /// <summary>
        /// Method starting the new game
        /// </summary>
        private void StartNewGame(GameSettings settings)
        {
            // set the new game settings
            _gameSettings = settings;

            // prepare the board and notify its change
            _board.NewGame();
            _events.GetEvent<BoardChanged>().Publish(null);

            // set the computer label
            if (_gameSettings.GameMode == eGameMode.ComputerGame)
            {
                // resolve the computer direction
                ePuckDir computerPuckDirection = _gameSettings.FirstToMove == eFirstToMove.Computer ? ePuckDir.Up : ePuckDir.Down;
                
                // notify the change
                PlayerNameRecord rec = new PlayerNameRecord();
                rec.PuckDirection = computerPuckDirection;
                rec.PlayerName = "Počítač";

                _events.GetEvent<SetPlayerName>().Publish(rec);
            }

            // check, if computer moves first
            if (_gameSettings.GameMode == eGameMode.ComputerGame && _gameSettings.FirstToMove == eFirstToMove.Computer)
            {
                // invoke the computation of a move by the engine
                ComputeMoveByEngine();
            }
        }

        /// <summary>
        /// Move step fired event handler fired
        /// </summary>
        private void MoveStepFiredHandler(eMoveDirection direction)
        {
            // check move step validity - key handler in the main window cannot check it
            if (_board.CanPlayInThisDirection(direction) == false)
                return;

            _moveDirection = direction;

            // block input
            _events.GetEvent<SetInputBlocking>().Publish(true);

            // notify animation
            _events.GetEvent<AnimatePuckMovement>().Publish(direction);
        }

        /// <summary>
        /// Handler when animation of puck has finished
        /// </summary>
        private void PuckAnimationFinished()
        {
            // play the move step            
            _board.Play(_moveDirection, out isFinal);

            // notify the board has changed
            _events.GetEvent<BoardChanged>().Publish(null);
            CommandManager.InvalidateRequerySuggested();

            // check, if we are animating engine move
            if (_engineMove.Count > 0)
            {
                // remove the first direction
                _moveDirection = _engineMove[0];
                _engineMove.RemoveAt(0);

                // notify animation
                _events.GetEvent<AnimatePuckMovement>().Publish(_moveDirection);

                return;
            }

            // check the end of game
            if (_board.HasGameFinished())
            {
                // unblock input
                _engineMoveFlag = false;
                _events.GetEvent<SetInputBlocking>().Publish(false);

                // if true, notify about the end of game
                _events.GetEvent<GetPlayerName>().Publish(_board.GetWinner().Value);

                return;
            }


            if (isFinal)
            {
                // run puck rotation animation
                _events.GetEvent<AnimatePuckRotation>().Publish(null);
            }
            else
            {
                // unblock input
                _engineMoveFlag = false;
                _events.GetEvent<SetInputBlocking>().Publish(false);
            }
        }

        /// <summary>
        /// Rotation animation finished
        /// </summary>
        private void RotationAnimationFinished()
        {
            if(_engineMoveFlag == false && isFinal)
            {
                // check, if we are running the computer game and there is not the end of game
                if (_gameSettings.GameMode == eGameMode.ComputerGame 
                            && _board.HasGameFinished() == false)
                {
                    ComputeMoveByEngine();
                    return;
                }
            }


            // unblock input
            _engineMoveFlag = false;
            _events.GetEvent<SetInputBlocking>().Publish(false);

            // check the end of game
            if (_board.HasGameFinished())
            {
                // if true, notify about the end of game
                _events.GetEvent<GetPlayerName>().Publish(_board.GetWinner().Value);
            }
        }

        /// <summary>
        /// Starts the engine move animation
        /// </summary>
        /// <param name="movePath"></param>
        private void AnimateEngineMove(eMoveDirection[] movePath)
        {
            _engineMoveFlag = true;

            // set the move
            _engineMove = movePath.ToList();

            // remove the first direction
            _moveDirection = _engineMove[0];
            _engineMove.RemoveAt(0);

            // notify animation
            _events.GetEvent<AnimatePuckMovement>().Publish(_moveDirection);
        }

        /// <summary>
        /// Method run when there is request to run computation of a move by engine
        /// </summary>
        private void ComputeMoveByEngine()
        {
            // notify to start the progress
            _events.GetEvent<StartProgress>().Publish(null);

            // compute the move by engine
            _engine.StartMoveComputing(_gameSettings.EngineLevel);
        }

        /// <summary>
        /// Handler for engine completed move
        /// </summary>
        void Engine_MoveComputedEventHandler(object sender, ComputedMoveEventArgs e)
        {
            // animation the move
            _dispatcher.BeginInvoke(new Action(() =>
                {
                    // terminate progress
                    _events.GetEvent<TerminateProgress>().Publish(null);

                    // run animation
                    AnimateEngineMove(e.Move);
                }), DispatcherPriority.Normal);
        }

    }
}
